package com.tymoshenko.seabattle.client.presenter;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlexTable;
import com.tymoshenko.seabattle.client.ClientFactory;
import com.tymoshenko.seabattle.client.rpc.shooting.ShootingService;
import com.tymoshenko.seabattle.client.rpc.shooting.ShootingServiceAsync;
import com.tymoshenko.seabattle.client.view.DashboardView;
import com.tymoshenko.seabattle.shared.model.domain.*;

import java.util.List;

/**
 * @author Yakiv Tymoshenko
 * @since 08.02.13
 */
public class ShootingPresenterImpl implements DashboardView.ShootingPresenter {

    private DashboardView dashboardView;
    private ClientFactory clientFactory;
    private ShootingServiceAsync shootingService;
    private boolean isHumanTurn = true;
    private boolean gameOver = false;

    public ShootingPresenterImpl(DashboardView dashboardView, ClientFactory clientFactory) {
        this.dashboardView = dashboardView;
        this.clientFactory = clientFactory;
        shootingService = GWT.create(ShootingService.class);
        dashboardView.setShootingPresenter(this);

        if (shootingService == null) {
            shootingService = GWT.create(ShootingService.class);
        }
    }

    public void startNewGame() {
        gameOver = false;
        isHumanTurn = true;
    }

    public void shoot(Point target, Player opponent) {
        if (isHumanTurn && !gameOver) {
            Board.Status lastShotStatus = processShot(target, clientFactory.getPlayer2());
            if (!Board.Status.DECK.equals(lastShotStatus)) {
                computerShot();
            }
        }
    }

    private void computerShot() {
        if (!isHumanTurn) {
            final Player humanPlayer = clientFactory.getPlayer1();
            AsyncCallback<List<Point>> fetchTargetsArrayAndDrawShotsCallback = new AsyncCallback<List<Point>>() {
                public void onFailure(Throwable caught) {
                    Window.alert(caught.getMessage());
                }

                public void onSuccess(final List<Point> result) {
                    final int totalTargets = result.size();

                    Timer timer = new Timer() {
                        int index = 0;

                        @Override
                        public void run() {
                            if (index == totalTargets) {
                                isHumanTurn = true;
                                cancel();
                                return;
                            }
                            processShot(result.get(index), humanPlayer);
                            ++index;
                        }
                    };
                    timer.scheduleRepeating(1000);
                }
            };
            shootingService.returnComputerTargets(humanPlayer, fetchTargetsArrayAndDrawShotsCallback);
        }
    }

    private Board.Status processShot(Point target, Player opponent) {
        if (gameOver) {
            return null;
        }
        Board opponentBoard = opponent.getBoard();
        Fleet opponentFleet = opponent.getFleet();
        FlexTable opponentBoardView = opponent == clientFactory.getPlayer1() ? dashboardView.getOwnBoard() : dashboardView.getOpponentBoard();

        Board.Status targetStatus = opponentBoard.getFields().get(target);
        if (targetStatus == Board.Status.DECK) {
            applyStatus(target, Board.Status.DAMAGED, opponentBoard, opponentBoardView);
            Ship shipUnderAttack = opponentFleet.getShipByDeck(target);
            if (isDestroyed(shipUnderAttack, opponentBoard)) {
                for (Point deck : shipUnderAttack.getDecks()) {
                    applyStatus(deck, Board.Status.DESTROYED, opponentBoard, opponentBoardView);
                }
                for (Point neighbour : Board.shipNeighbourFields(shipUnderAttack)) {
                    applyStatus(neighbour, Board.Status.MISSED, opponentBoard, opponentBoardView);
                }
                opponentFleet.destroyShip(shipUnderAttack);
                // Check if game over
                if (opponent.getFleet().isDestroyed()) {
                    gameOver = true;
                    Player winner = clientFactory.getPlayer1() == opponent ? clientFactory.getPlayer2() : clientFactory.getPlayer1();
                    Window.alert(winner.getAccount().getFirstName() + " has won!");
                }
            }
        } else if (targetStatus == Board.Status.FREE || targetStatus == Board.Status.BORDER) {
            applyStatus(target, Board.Status.MISSED, opponentBoard, opponentBoardView);
            isHumanTurn = false;
        } else {
            isHumanTurn = true;
            Window.alert("Unable to shoot here");
        }

        return targetStatus;
    }

    private boolean isDestroyed(Ship ship, Board board) {
        if (ship != null) {
            for (Point deck : ship.getDecks()) {
                if (board.getFields().get(deck) != Board.Status.DAMAGED) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    private void applyStatus(Point target, Board.Status status, Board board, FlexTable table) {
        board.setFieldStatus(target, status);

        final String styleName;
        if (status == Board.Status.DAMAGED) {
            styleName = "gameBoardCellShipDeckDamaged";
        } else if (status == Board.Status.DESTROYED) {
            styleName = "gameBoardCellShipDeckDestroyed";
        } else {
            styleName = "gameBoardCellMissedShot";
        }
        table.getCellFormatter().setStyleName(target.getRow() - 1, target.getCol() - 1, styleName);
    }
}
